﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using CyUSB;
using System.Collections.Concurrent;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Threading;
using Microsoft.Win32;
using System.Diagnostics.PerformanceData;
using System.Diagnostics;


namespace MuonDaq
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        USBDeviceList usbDevices;
        CyUSBDevice MyDevice;
        CyBulkEndPoint BulkInEndPoint;
        byte InEndPointAddr;
        CyBulkEndPoint BulkOutEndPoint;
        byte OutEndPointAddr;
        bool IsUsbConfig = false;

        //
        bool bRunning = false;
        private delegate void DisplayPacketInfo(int packageNum, int Failure, double Speed);
        private delegate void UpdateLoopNum(int num);
        private delegate void DisplayOnlineInfo(bool ready);

        string AbsoluteFileName = null;
        private long Successes;
        private long Failures;
        private DateTime t1, t2;
        private TimeSpan elapsed;
        double XferBytes;
        double xferRate;
        int BufSz;
        int QueueSz = 1;
        int PacketNr;
        int RunId = 0;
        int AcqInterval=120;
        bool IsLittleEndian = true;
        BlockingCollection<byte[]> ThreadData = new BlockingCollection<byte[]>();

        // FEC & DAQ: configuration flag and param
        uint LinkUsed = 0;
        bool IsLinkConnected = false;
        uint LayerNr = 0;
        bool IsAsicConfig = false;
        bool IsDaqConfig = false;
        bool IsPedestalMode = true;
        bool IsProcessData = true;
        bool IsPushData = true;
        bool IsCustomThresh = false;
        bool IsFirtDaqLoop = true;
        string PedestalPath = null;
        bool IsStdPedestal = true;

        const string fOnlineDir = "online";

        public MainWindow()
        {
            InitializeComponent();

            // Create the list of USB devices attached to the CyUSB3.sys driver.
            usbDevices = new USBDeviceList(CyConst.DEVICES_CYUSB);
            usbDevices.DeviceAttached += new EventHandler(usbDevices_DeviceAttached);
            usbDevices.DeviceRemoved += new EventHandler(usbDevices_DeviceRemoved);

            // Discover the DAQ board
            RefreshUsb();

            // unix t windows
            ConverProcessFromDos2Unix();

            // init runid
            InitRunId();
        }

        private void InitRunId()
        { 
            //todo: read from config file
        }

        private void SetRunId(int run_id)
        {
            RunId = run_id;
            tbxRunId.Text = run_id.ToString();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            MessageBoxResult key = MessageBox.Show(
                "Are you sure you want to quit",
                "Confirm",
                MessageBoxButton.YesNo,
                MessageBoxImage.Question,
                MessageBoxResult.No);
            e.Cancel = (key == MessageBoxResult.No);
        }

        ////// USB device management //////
        void usbDevices_DeviceRemoved(object sender, EventArgs e)
        {
            bRunning = false;

            if (!ThreadData.IsAddingCompleted)
                ThreadData.CompleteAdding();

            MyDevice = null;
            BulkInEndPoint = null;
            BulkOutEndPoint = null;
            RefreshUsb();

            // UI binding
            UsbDisabled();

        }

        void usbDevices_DeviceAttached(object sender, EventArgs e)
        {
            if (!IsUsbConfig)
            {
                RefreshUsb();

                cbxDeviceLists.IsEnabled = true;
                cbxInEndPoint.IsEnabled = true;
                cbxOutEndPoint.IsEnabled = true;
                cbxPacketNr.IsEnabled = true;
                cbxXferQueue.IsEnabled = true;
            }
            else
            {
                MessageBox.Show("New DAQ board attached while existing one in use", "WARNING", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        void RefreshUsb()
        {
            RefreshDeviceLists(false);
            RefreshEndPoint();
        }

        private void RefreshDeviceLists(bool bPreserveSelectDevice)
        {
            if (cbxDeviceLists.Items.Count > 0)
            {
                cbxDeviceLists.Items.Clear();
            }
            int nDeviceList = usbDevices.Count;
            for (int nCount = 0; nCount < nDeviceList; nCount++)
            {
                USBDevice fxDevice = usbDevices[nCount];
                string strmsg;
                strmsg = "(0x" + fxDevice.VendorID.ToString("X4") + "-0x" + fxDevice.ProductID.ToString("X4") + ") " + fxDevice.FriendlyName;
                cbxDeviceLists.Items.Add(strmsg);
            }
        }

        private void RefreshEndPoint()
        {
            cbxInEndPoint.Items.Clear();
            cbxOutEndPoint.Items.Clear();
            USBDevice dev = null;

            //
            if (cbxDeviceLists.Items.Count > 0 && cbxDeviceLists.SelectedIndex != -1)
                dev = usbDevices[cbxDeviceLists.SelectedIndex];

            if (dev != null)
            {
                MyDevice = (CyUSBDevice)dev;
                if (MyDevice.BulkInEndPt == null || MyDevice.BulkOutEndPt == null)
                {
                    if (MyDevice.FriendlyName == "Cypress FX3 USB BootLoader Device") // download the firmware
                    {
                        ResetUsbDevice();
                        string ConfigFilename = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "firmware", "SF_streamIN.img");
                        DownloadDeviceFirmware(ConfigFilename);
                    }
                    else
                    {
                        MessageBox.Show("Not bulk device. Please program the device", "USB ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                    return;
                }
                InEndPointAddr = MyDevice.BulkInEndPt.Address;
                OutEndPointAddr = MyDevice.BulkOutEndPt.Address;
                string InEndPoint = "0x" + InEndPointAddr.ToString("X") + " Size: " + MyDevice.BulkInEndPt.MaxPktSize.ToString();
                string OutEndPoint = "0x" + OutEndPointAddr.ToString("X") + " Size: " + MyDevice.BulkOutEndPt.MaxPktSize.ToString();
                cbxInEndPoint.Items.Add(InEndPoint);
                cbxOutEndPoint.Items.Add(OutEndPoint);
                cbxPacketNr.Text = "16"; //Set default value to 64 Packets
                cbxXferQueue.Text = "4";
                if (cbxInEndPoint.Items.Count > 0 && cbxOutEndPoint.Items.Count > 0)
                {
                    btnSetUsb.IsEnabled = true;
                    cbxInEndPoint.SelectedIndex = 0;
                    cbxOutEndPoint.SelectedIndex = 0;
                }
                else
                    btnSetUsb.IsEnabled = false;
            }
        }

        private void btnSetUsb_Click(object sender, RoutedEventArgs e)
        {
            if (btnSetUsb.Content.Equals("Set"))
            {
                if (cbxDeviceLists.SelectedIndex == -1)
                {
                    MessageBox.Show("Please select the USB device", "USB ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                if (SetDevice())
                {
                    UsbEnabled();
                }
                else
                {
                    MessageBox.Show("No Bulk EndPoint. Please program the Device", "Device ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            else
            {
                MyDevice = null;
                BulkInEndPoint = null;
                BulkOutEndPoint = null;
                UsbDisabled();
            }
        }

        private bool SetDevice()
        {
            MyDevice = usbDevices[cbxDeviceLists.SelectedIndex] as CyUSBDevice;
            if (MyDevice != null && MyDevice.BulkInEndPt != null && MyDevice.BulkOutEndPt != null)
            {
                BulkInEndPoint = MyDevice.EndPointOf(InEndPointAddr) as CyBulkEndPoint;
                BulkOutEndPoint = MyDevice.EndPointOf(OutEndPointAddr) as CyBulkEndPoint;
                lblUsbName.Content = "Connected";
                lblUsbName.Foreground = Brushes.Green;
                IsUsbConfig = true;
                return true;
            }
            return false;
        }
        private void UsbEnabled()
        {
            cbxDeviceLists.IsEnabled = false;
            cbxInEndPoint.IsEnabled = false;
            cbxOutEndPoint.IsEnabled = false;

            ShowText("USB Connect");
            btnSetUsb.Content = "Reset";

            gbxAsic.IsEnabled = true;
            gbxDaq.IsEnabled = true;
            btnStartAcq.IsEnabled = true;
            btnCheckLink.IsEnabled = true;
            btnConfigAsic.IsEnabled = true;
            btnConfigDaq.IsEnabled = true;
        }

        private void UsbDisabled()
        {
            cbxDeviceLists.IsEnabled = true;
            cbxInEndPoint.IsEnabled = true;
            cbxOutEndPoint.IsEnabled = true;


            tbxInfo.AppendText("== USB Disconnect\n");
            lblUsbName.Content = "No Device";
            lblUsbName.Foreground = Brushes.Red;
            gbxAsic.IsEnabled = false;
            gbxDaq.IsEnabled = false;
            btnStartAcq.IsEnabled = false;
            btnSetUsb.Content = "Set";
            btnCheckLink.IsEnabled = false;
            btnConfigAsic.IsEnabled = false;
            btnConfigDaq.IsEnabled = false;

            lblLinkStatus.Content = "Unconnected";
            IsLinkConnected = false;
            lblLinkStatus.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));

            lblAsicStatus.Content = "Unconfigured";
            IsAsicConfig = false;
            lblAsicStatus.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));

            IsDaqConfig = false;
            IsUsbConfig = false;
            btnConfigDaq.Background = new SolidColorBrush(Color.FromRgb(199, 62, 58));
            btnConfigAsic.Background = new SolidColorBrush(Color.FromRgb(199, 62, 58));

        }

        private void cbxDeviceLists_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            RefreshEndPoint();
        }

        private void ResetUsbDevice()
        {
            if (MyDevice != null)
            {
                MyDevice.Reset();
            }
            else
            {
                MessageBox.Show("Please select the USB device", "Device ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Download the firmware into MCU
        private bool DownloadDeviceFirmware(string ConfigFilename)
        {
            if (ConfigFilename == null)
            {
                return false;
            }
            if (File.Exists(ConfigFilename))
            {
                DownloadFX3Firmware(ConfigFilename, MyDevice);
                return true;
            }
            else
            {
                MessageBox.Show("Config file " + ConfigFilename + " do not exist! Please config USB manually!", "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                return false;
            }
        }

        private void DownloadFX3Firmware(string FileName, CyUSBDevice myDevice)
        {
            if (MyDevice != null)
            {
                FX3_FWDWNLOAD_ERROR_CODE enmResult = FX3_FWDWNLOAD_ERROR_CODE.SUCCESS;
                CyFX3Device fx3 = myDevice as CyFX3Device;
                if (fx3 != null)
                {
                    enmResult = fx3.DownloadFw(FileName, FX3_FWDWNLOAD_MEDIA_TYPE.RAM);
                }
                else
                {
                    MessageBox.Show("Please check whether the device is FX3 device", "Device ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
                if (enmResult != FX3_FWDWNLOAD_ERROR_CODE.SUCCESS)
                {
                    MessageBox.Show("Config error", "Device ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                }
                string report = string.Format("Program {0}", fx3.GetFwErrorString(enmResult));
                ShowText(report);
            }
            else
            {
                MessageBox.Show("Please select the USB device", "Device ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
        /////

        ///// Raw Data save UI and uitlity ////
        private bool SaveFile(string filePath, string fileName, int idx = 0)
        {
            string report;
            if (filePath.Trim() == null)
            {
                MessageBox.Show("Path not exist", "File ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                return false;
            }
            if (!Directory.Exists(filePath))
            {
                Directory.CreateDirectory(filePath);
                report = string.Format("Dictory \"{0}\" created", filePath);
                ShowText(report);
            }
            string DefaultFileName = DateTime.Now.ToString("yyyyMMdd'_'HHmmss");
            string absoluteFileName;
            DefaultFileName = DefaultFileName.Replace("/", "_");
            DefaultFileName = DefaultFileName.Replace(":", "_");
            DefaultFileName = DefaultFileName.Replace(" ", "-");
            DefaultFileName = "fec_" + DefaultFileName;
            if (fileName.Trim() == null || fileName == ".dat" || fileName == "")
            {
                tbxFileName.Text = DefaultFileName;
                fileName = DefaultFileName;
            }
            absoluteFileName = System.IO.Path.Combine(filePath, fileName);
            if (File.Exists(absoluteFileName + "-0.dat"))
            {
                MessageBoxResult MBresult = MessageBox.Show("文件 " + fileName + "-0.dat 已存在.\n 点击Yes使用默认文件名.\n 点击No覆盖当前文件.\n 点击Cancel取消当前运行", "File Error", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
                if (MBresult == MessageBoxResult.Yes)
                {
                    absoluteFileName = System.IO.Path.Combine(filePath, DefaultFileName);
                    //fileStream = File.Create(absoluteFileName);
                    tbxFileName.Text = DefaultFileName;
                }
                else if (MBresult == MessageBoxResult.No)
                {
                    File.Delete(absoluteFileName + "-0.dat");
                    //tbxFileName.Text = absoluteFileName;
                }
                else
                {
                    return false;
                }
            }
            AbsoluteFileName = absoluteFileName;
            return true;
        }

        private bool SaveFile(string fileName)
        {
            return SaveFile(tbxReceiveDataPath.Text, fileName);
        }

        private bool SaveFile()
        {
            return SaveFile(tbxFileName.Text);
        }

        private void btnSelectSavePath_Click(object sender, RoutedEventArgs e)
        {
            var fldrDlg = new System.Windows.Forms.FolderBrowserDialog();
            fldrDlg.SelectedPath = tbxReceiveDataPath.Text;

            if (fldrDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                tbxReceiveDataPath.Text = fldrDlg.SelectedPath;
            }
        }
        /////

        ///// Data acquisition UI and utility /////
        private bool CheckPacketNr()
        {
            if (BulkInEndPoint == null && BulkOutEndPoint == null)
                return false;

            //
            int PacketNr = Convert.ToUInt16(cbxPacketNr.Text);
            int len = BulkInEndPoint.MaxPktSize * PacketNr;
            int maxLen = 0x400000; // 4MBytes
            if (len > maxLen)
            {
                if (BulkInEndPoint.MaxPktSize == 0)
                {
                    MessageBox.Show("Please correct MaxPacketSize in Descriptor", "Invalid MaxPacketSize");
                    return false;
                }
                MessageBox.Show("Maximum of 4MB per transfer.  Packets reduced.", "Invalid Packets per Xfer. Aleady set to max.");
                int max = maxLen / BulkInEndPoint.MaxPktSize;
                cbxPacketNr.Text = max.ToString();
                return false;

            }
            return true;
        }

        private async void btnStartAcq_Click(object sender, RoutedEventArgs e)
        {
            if (btnStartAcq.Content.Equals("Start"))
            {
                // 1. check configuration finished
                // 1.1 link
                if (!IsLinkConnected)
                {
                    if (MessageBox.Show("Links have not been checked. Continue?", "Warning", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.No)
                    {
                        return;
                    }
                }
                
                // 1.2 asic registers
                if (!IsAsicConfig)
                {
                    if (MessageBox.Show("FECs have not been configed. Continue?", "Warning", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.No)
                    {
                        return;
                    }
                }

                // 1.3 daq configs: trigger mode ,threshold loading etc.
                if (!IsDaqConfig)
                {
                    if (MessageBox.Show("DAQ has not been configed. Continue?", "Warning", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.No)
                    {
                        return;
                    }
                }

                // 1.4 USB transfer setting
                if (!CheckPacketNr()) // xfer setting
                { 
                    return; 
                }
                else
                {
                    BufSz = BulkInEndPoint.MaxPktSize * short.Parse(cbxPacketNr.Text); // buffer size for a single thread
                    QueueSz = int.Parse(cbxXferQueue.Text); // nr of threads
                    PacketNr = int.Parse(cbxPacketNr.Text);
                }

                // 1.5 data storage setting
                if (!SaveFile()) // output file
                {
                    return;
                }

                ShowText("DAQ Start Data Xfer");
                // 1.6 online data process preparation
                if (IsProcessData)
                {
                    CleanOnlineDirectory();
                }

                // 3. Transfer configure
                // 3.1 buffer preparation
                ThreadData = new BlockingCollection<byte[]>(556);
                // 3.2 data storage interval and loop counter init
                if (!CheckStringFormat.CheckInteger(tbxAcqTime.Text))
                {
                    MessageBox.Show("Acquisition illegal. Value must be integer", "Text ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                    AcqInterval = 3600;
                }
                else
                {
                    AcqInterval = int.Parse(tbxAcqTime.Text);
                }
                // 3.3 runid
                if (!IsPedestalMode)
                {
                    if (!CheckStringFormat.CheckInteger(tbxRunId.Text))
                    {
                        MessageBox.Show("Acquisition illegal. RunId must be valid interger", "Text ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                        return;
                    }
                    else
                    {
                        RunId = int.Parse(tbxRunId.Text);
                    }
                }
              
                //3.4 binary stream writer
                BinaryWriter bw = new BinaryWriter(File.Open(AbsoluteFileName + "-0" + ".dat", FileMode.Append));
                lblFileLoop.Content = "0";
                IsFirtDaqLoop = true;

                // 3.5 transfer status updator init
                UpdateLoopNum LoopNum = new UpdateLoopNum((int num) => UpdateLoop(num));

                // 4. Start transfer tasks
                // 4.1 Fisrt, start consumer task for data storage
                // bRunning = true;
                Task saveTask = Task.Factory.StartNew(() =>
                {
                    int AcqLoop = 0;
                    byte[] DataToFile;
                    int PkgLength = 0;

                    DateTime TStart = DateTime.Now;
                    DateTime TStop = DateTime.Now;
                    TimeSpan AcqTime;

                    // as long as there is data in the buffer, the task will keep running
                    foreach (byte[] item in ThreadData.GetConsumingEnumerable())
                    {
                        TStop = DateTime.Now;
                        AcqTime = TStop - TStart;

                        // automatically flush the existing data into disk and open a new file with increased file-id if fixed interval setted
                        if (AcqTime.TotalSeconds > AcqInterval)
                        {
                            // flush current file
                            bw.Flush();
                            bw.Dispose();
                            bw.Close();

                            // decode current file
                            if (IsProcessData)
                            {
                                OnlineRecProcess(AbsoluteFileName + "-" + AcqLoop.ToString() + ".dat");
                            }

                            // increase the file-id
                            AcqLoop++;
                            IsFirtDaqLoop = false;

                            bw = new BinaryWriter(File.Open(AbsoluteFileName + "-" + AcqLoop.ToString() + ".dat", FileMode.Append));
                            TStart = DateTime.Now;
                            Dispatcher.Invoke(LoopNum, AcqLoop);
                        }

                        // 
                        DataToFile = item;
                        PkgLength = DataToFile.Length;
                        byte[] FileBuffer = new byte[PkgLength];
                        for (int i = 0; i < PkgLength; i++)
                        {
                            FileBuffer[i] = DataToFile[i]; // big-endian by default   
                        }
                        bw.Write(FileBuffer);
                    }
                    bw.Flush();
                    bw.Dispose();
                    bw.Close();
                });
                
                // 4.2 Then, start the producer task 
                Thread.Sleep(1000);
                bRunning = true; // flag controlling the data transfer state
                var producerTask = Task.Factory.StartNew(() => AsyncDataProducer());

                /* the followings are commands sent to the FEC and DAQ board */
                // 5. [?]
                TransmitStatusChange(true);

                // 6. start FEC
                StartFecDataTransfer();
                Thread.Sleep(100);

                // 7. enable trigger generation
                TriggerEnable();

                // 2. UI binding
                gbxLink.IsEnabled = false;
                gbxAsic.IsEnabled = false;
                gbxDaq.IsEnabled = false;
                splXferStatus.IsEnabled = true;
                btnStartAcq.Content = "Stop";
                btnStartAcq.Background = new SolidColorBrush(Color.FromRgb(181, 93, 76));
                btnSetUsb.IsEnabled = false;
                cbxPacketNr.IsEnabled = false;
                cbxXferQueue.IsEnabled = false;
                cbxDataProcessOrNot.IsEnabled = false;
                splOnlineStatus.IsEnabled = true;

                ShowText("DAQ Xfer in process ...");
            }   
            else { // stop
                // 1. stop trigger
                TriggerDisable();
                Thread.Sleep(100);

                // 2. stop FEC
                StopFecDataTransfer();
                Thread.Sleep(100);

                // 3. stop data transfer first
                bRunning = false;
                Thread.Sleep(100);

                // 4. [?]
                TransmitStatusChange(false);
                Thread.Sleep(100);

                // 5. do same after work before ready to be started again
                btnStartAcq.Content = "Stopping...";
                btnStartAcq.Background = new SolidColorBrush(Color.FromRgb(219, 77, 109));

                // 5.1 abort USB EP transfer
                for (int i = 0; i < QueueSz; i++)
                {
                    BulkInEndPoint.Abort();
                }

                // 5.2 process the last package
                if (IsProcessData)
                {
                    ShowText("Processing remaining data packets");
                    if (IsPedestalMode)
                    {
                        CalcNoiseProcess();
                    }
                    else
                    {
                        OnlineRecProcess(AbsoluteFileName + "-" + lblFileLoop.Content + ".dat");
                    }
                }

                // 5.3 USB Endpoint reset
                await Task.Run(() => RstUsbData());

                // 5.4 Reset USB FSM  when ACQ done to initial the data sent counter
                SendText("0x00F00001");

                // 6. UI binding
                gbxLink.IsEnabled = true;
                gbxAsic.IsEnabled = true;
                gbxDaq.IsEnabled = true;
                splXferStatus.IsEnabled = false;
                btnStartAcq.Content = "Start";
                btnStartAcq.Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                btnSetUsb.IsEnabled = true;
                cbxPacketNr.IsEnabled = true;
                cbxXferQueue.IsEnabled = true;
                cbxDataProcessOrNot.IsEnabled = true;

                splOnlineStatus.IsEnabled = false;
                UpdateOnlineStatus(false);

                SetRunId(RunId + 1);
                ShowText("DAQ Xfer Stopped!");
            }
        }

        private void AsyncDataProducer()
        {
            // Setup the queue buffers
            byte[][] cmdBufs = new byte[QueueSz][];
            byte[][] xferBufs = new byte[QueueSz][];
            byte[][] ovLaps = new byte[QueueSz][];
            ISO_PKT_INFO[][] pktsInfo = new ISO_PKT_INFO[QueueSz][];

            //int xStart = 0;

            //////////////////////////////////////////////////////////////////////////////
            ///////////////Pin the data buffer memory, so GC won't touch the memory///////
            //////////////////////////////////////////////////////////////////////////////

            GCHandle cmdBufferHandle = GCHandle.Alloc(cmdBufs[0], GCHandleType.Pinned);
            GCHandle xFerBufferHandle = GCHandle.Alloc(xferBufs[0], GCHandleType.Pinned);
            GCHandle overlapDataHandle = GCHandle.Alloc(ovLaps[0], GCHandleType.Pinned);
            GCHandle pktsInfoHandle = GCHandle.Alloc(pktsInfo[0], GCHandleType.Pinned);

            try
            {
                LockNLoad(cmdBufs, xferBufs, ovLaps, pktsInfo);
            }
            catch (NullReferenceException e)
            {
                // This exception gets thrown if the device is unplugged 
                // while we're streaming data
                e.GetBaseException();
                //this.Invoke(handleException);
            }

            //////////////////////////////////////////////////////////////////////////////
            ///////////////Release the pinned memory and make it available to GC./////////
            //////////////////////////////////////////////////////////////////////////////
            cmdBufferHandle.Free();
            xFerBufferHandle.Free();
            overlapDataHandle.Free();
            pktsInfoHandle.Free();
        }

        public unsafe void LockNLoad(byte[][] cBufs, byte[][] xBufs, byte[][] oLaps, ISO_PKT_INFO[][] pktsInfo)
        {
            GCHandle[] bufSingleTransfer = new GCHandle[QueueSz];
            GCHandle[] bufDataAllocation = new GCHandle[QueueSz];
            GCHandle[] bufPktsInfo = new GCHandle[QueueSz];
            GCHandle[] handleOverlap = new GCHandle[QueueSz];

            int j = 0;
            int nLocalCount = 0;
            while (j < QueueSz)
            {
                // Allocate one set of buffers for the queue, Buffered IO method require user to allocate a buffer as a part of command buffer,
                // the BeginDataXfer does not allocated it. BeginDataXfer will copy the data from the main buffer to the allocated while initializing the commands.
                cBufs[j] = new byte[CyConst.SINGLE_XFER_LEN];

                xBufs[j] = new byte[BufSz];

                //initialize the buffer with initial value 0xA5
                for (int iIndex = 0; iIndex < BufSz; iIndex++)
                    xBufs[j][iIndex] = 0;

                int sz = Math.Max(CyConst.OverlapSignalAllocSize, sizeof(OVERLAPPED));
                oLaps[j] = new byte[sz];
                pktsInfo[j] = new ISO_PKT_INFO[PacketNr];

                /*/////////////////////////////////////////////////////////////////////////////
                 * 
                 * fixed keyword is getting thrown own by the compiler because the temporary variables 
                 * tL0, tc0 and tb0 aren't used. And for jagged C# array there is no way, we can use this 
                 * temporary variable.
                 * 
                 * Solution  for Variable Pinning:
                 * Its expected that application pin memory before passing the variable address to the
                 * library and subsequently to the windows driver.
                 * 
                 * Cypress Windows Driver is using this very same memory location for data reception or
                 * data delivery to the device.
                 * And, hence .Net Garbage collector isn't expected to move the memory location. And,
                 * Pinning the memory location is essential. And, not through FIXED keyword, because of 
                 * non-usability of temporary variable.
                 * 
                /////////////////////////////////////////////////////////////////////////////*/
                //fixed (byte* tL0 = oLaps[j], tc0 = cBufs[j], tb0 = xBufs[j])  // Pin the buffers in memory
                //////////////////////////////////////////////////////////////////////////////////////////////
                bufSingleTransfer[j] = GCHandle.Alloc(cBufs[j], GCHandleType.Pinned);
                bufDataAllocation[j] = GCHandle.Alloc(xBufs[j], GCHandleType.Pinned);
                bufPktsInfo[j] = GCHandle.Alloc(pktsInfo[j], GCHandleType.Pinned);
                handleOverlap[j] = GCHandle.Alloc(oLaps[j], GCHandleType.Pinned);
                // oLaps "fixed" keyword variable is in use. So, we are good.
                /////////////////////////////////////////////////////////////////////////////////////////////            

                unsafe
                {
                    //fixed (byte* tL0 = oLaps[j])
                    {
                        CyUSB.OVERLAPPED ovLapStatus = new CyUSB.OVERLAPPED();
                        ovLapStatus = (CyUSB.OVERLAPPED)Marshal.PtrToStructure(handleOverlap[j].AddrOfPinnedObject(), typeof(CyUSB.OVERLAPPED));
                        ovLapStatus.hEvent = (IntPtr)PInvoke.CreateEvent(0, 0, 0, 0);
                        Marshal.StructureToPtr(ovLapStatus, handleOverlap[j].AddrOfPinnedObject(), true);

                        // Pre-load the queue with a request
                        int len = BufSz;
                        if (BulkInEndPoint.BeginDataXfer(ref cBufs[j], ref xBufs[j], ref len, ref oLaps[j]) == false)
                            Failures++;
                    }
                    j++;
                }
            }

            XferData(cBufs, xBufs, oLaps, pktsInfo, handleOverlap);          // All loaded. Let's go!

            unsafe
            {
                for (nLocalCount = 0; nLocalCount < QueueSz; nLocalCount++)
                {
                    CyUSB.OVERLAPPED ovLapStatus = new CyUSB.OVERLAPPED();
                    ovLapStatus = (CyUSB.OVERLAPPED)Marshal.PtrToStructure(handleOverlap[nLocalCount].AddrOfPinnedObject(), typeof(CyUSB.OVERLAPPED));
                    PInvoke.CloseHandle(ovLapStatus.hEvent);

                    /*////////////////////////////////////////////////////////////////////////////////////////////
                     * 
                     * Release the pinned allocation handles.
                     * 
                    ////////////////////////////////////////////////////////////////////////////////////////////*/
                    bufSingleTransfer[nLocalCount].Free();
                    bufDataAllocation[nLocalCount].Free();
                    bufPktsInfo[nLocalCount].Free();
                    handleOverlap[nLocalCount].Free();

                    cBufs[nLocalCount] = null;
                    xBufs[nLocalCount] = null;
                    oLaps[nLocalCount] = null;
                }
            }
            GC.Collect();
        }
        
        public unsafe void XferData(byte[][] cBufs, byte[][] xBufs, byte[][] oLaps, ISO_PKT_INFO[][] pktsInfo, GCHandle[] handleOverlap)
        {
            int k = 0;
            int len = 0;

            Successes = 0;
            Failures = 0;

            XferBytes = 0;
            t1 = DateTime.Now;
            long nIteration = 0;

            CyUSB.OVERLAPPED ovData = new CyUSB.OVERLAPPED();
            DisplayPacketInfo dp = new DisplayPacketInfo((int x, int y, double z) => UpdatePackageInfo(x, y, z));
            Dispatcher.Invoke(dp, (int)Successes, (int)Failures, (double)xferRate);

            // infinite loop of reading from USB as long as bRunning is true; queue by queue sequentially;
            for (; bRunning;)
            {
                nIteration++;

                // WaitForXfer
                unsafe
                {
                    {
                        ovData = (CyUSB.OVERLAPPED)Marshal.PtrToStructure(handleOverlap[k].AddrOfPinnedObject(), typeof(CyUSB.OVERLAPPED));
                        if (!BulkInEndPoint.WaitForXfer(ovData.hEvent, 0xFFFFFFFF))
                        {
                            BulkInEndPoint.Abort();
                            PInvoke.WaitForSingleObject(ovData.hEvent, 500);
                        }
                    }
                }

                // FinishDataXfer
                int PkgLength;
                if (BulkInEndPoint.FinishDataXfer(ref cBufs[k], ref xBufs[k], ref len, ref oLaps[k]))
                {
                    BulkInEndPoint.FinishDataXfer(ref cBufs[k], ref xBufs[k], ref len, ref oLaps[k]); // [?] why invoke it again
                    XferBytes += len;
                    if (len != 0)
                    {
                        byte[] DataToFile = new byte[len];
                        Array.Copy(xBufs[k], DataToFile, len);
                        ThreadData.Add(DataToFile);
                        PkgLength = DataToFile.Length;
                        Successes++;
                    }
                }
                else
                {
                    Failures++;
                }

                // Re-submit this buffer into the queue
                len = BufSz;
                if (BulkInEndPoint.BeginDataXfer(ref cBufs[k], ref xBufs[k], ref len, ref oLaps[k]) == false)
                {
                    //Failures++;
                }

                // update transfer status UI
                k++;
                if (k == QueueSz)  // Only update displayed stats once each time through the queue
                {
                    k = 0;

                    t2 = DateTime.Now;
                    elapsed = t2 - t1;

                    xferRate = XferBytes / elapsed.TotalMilliseconds;
                    xferRate = xferRate / (1000.0);
                    Dispatcher.Invoke(dp, (int)Successes, (int)Failures, (double)xferRate);

                    Thread.Sleep(0);
                }
                Thread.Sleep(0);

            } // End infinite loop

            // Let's recall all the queued buffer and abort the end point.
            if (BulkInEndPoint != null)
            {
                BulkInEndPoint.Abort();
            }
            ThreadData.CompleteAdding();
        }

        //////
        
        ////// DAQ status real time update UI and uitlity //////
        private void UpdatePackageInfo(int packageNum, int packageFailure, double Speed)
        {
            tbxPackageCount.Text = packageNum.ToString();
            tbxSpeed.Text = Speed.ToString("#0.00");
        }

        private void UpdateLoop(int loop)
        {
            lblFileLoop.Content = loop.ToString();
        }

        private void UpdateOnlineStatus(bool ready)
        {
            if (ready)
            {
                lblOnlineStatus.Content = "READY";
                lblOnlineStatus.Foreground = new SolidColorBrush(Color.FromRgb(0, 128, 0));
            }
            else
            {
                lblOnlineStatus.Content = "NULL";
                lblOnlineStatus.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));
            }
        }

        //////
        
       
        ////// Link Check ///////
        private void btnLink7_Click(object sender, RoutedEventArgs e)
        {
            if ((LinkUsed & 0x0080) == 0)
            {
                btnLink7.Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                LinkUsed |= 0x0080;
            }
            else
            {
                btnLink7.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
                LinkUsed &= 0xFF7F;
            }

            RefreshLink();
        }

        private void btnLink6_Click(object sender, RoutedEventArgs e)
        {
            if ((LinkUsed & 0x0040) == 0)
            {
                btnLink6.Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                LinkUsed |= 0x0040;
            }
            else
            {
                btnLink6.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
                LinkUsed &= 0xFFBF;
            }
            RefreshLink();
        }
        private void btnLink5_Click(object sender, RoutedEventArgs e)
        {
            if ((LinkUsed & 0x0020) == 0)
            {
                btnLink5.Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                LinkUsed |= 0x0020;
            }
            else
            {
                btnLink5.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
                LinkUsed &= 0xFFDF;
            }
            RefreshLink();
        }

        private void btnLink4_Click(object sender, RoutedEventArgs e)
        {
            if ((LinkUsed & 0x0010) == 0)
            {
                btnLink4.Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                LinkUsed |= 0x0010;
            }
            else
            {
                btnLink4.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
                LinkUsed &= 0xFFEF;
            }
            RefreshLink();
        }

        private void btnLink3_Click(object sender, RoutedEventArgs e)
        {
            if ((LinkUsed & 0x0008) == 0)
            {
                btnLink3.Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                LinkUsed |= 0x0008;
            }
            else
            {
                btnLink3.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
                LinkUsed &= 0xFFF7;
            }
            RefreshLink();
        }

        private void btnLink2_Click(object sender, RoutedEventArgs e)
        {
            if ((LinkUsed & 0x04) == 0)
            {
                btnLink2.Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                LinkUsed |= 0x0004;
            }
            else
            {
                btnLink2.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
                LinkUsed &= 0xFFFB;
            }
            RefreshLink();
        }

        private void btnLink1_Click(object sender, RoutedEventArgs e)
        {
            if ((LinkUsed & 0x02) == 0)
            {
                btnLink1.Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                LinkUsed |= 0x0002;
            }
            else
            {
                btnLink1.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
                LinkUsed &= 0xFFFD;
            }
            RefreshLink();
        }

        private void btnLink0_Click(object sender, RoutedEventArgs e)
        {
            if ((LinkUsed & 0x0001) == 0)
            {
                btnLink0.Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                LinkUsed |= 0x0001;
            }
            else
            {
                btnLink0.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
                LinkUsed &= 0xFFFE;
            }
            RefreshLink();

        }

        private void RefreshLink()
        {
            tbxLinkHex.Text = LinkUsed.ToString("X2");

        }

        private void tbxLinkHex_KeyUp(object sender, KeyEventArgs e)
        {
            if (tbxLinkHex.Text == "")
            {
                return;
            }
            Button[] linkConnet = new Button[8] { btnLink0, btnLink1, btnLink2, btnLink3, btnLink4, btnLink5, btnLink6, btnLink7};
            string HexString = tbxLinkHex.Text;
            if (CheckStringFormat.CheckHex(HexString) == false)
            {
                MessageBox.Show("The input value is not hex format", "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            if (HexString.Length > 4)
            {
                // Get the first 4 characters of HexString
                HexString = HexString.Substring(0, 4);
                tbxLinkHex.Text = HexString;
            }
            uint HexValue = uint.Parse(HexString, System.Globalization.NumberStyles.HexNumber);
            LinkUsed = HexValue;
            for (int i = 0; i < 8; i++)
            {
                if ((HexValue & (1 << i)) != 0)
                {
                    linkConnet[i].Background = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                }
                else
                {
                    linkConnet[i].Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
                }
            }
            lblLinkStatus.Content = "Unconnected";
            lblLinkStatus.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));
        }

        private void btnCheckLink_Click(object sender, RoutedEventArgs e)
        {
            if (LinkUsed == 0)
            {
                lblLinkStatus.Content = "Unconnected";
                lblLinkStatus.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));
            }

            // force FEC & DAQ configuration after link check
            IsAsicConfig = false;
            btnConfigAsic.Background = new SolidColorBrush(Color.FromRgb(199, 62, 58));
            IsDaqConfig = false;
            btnConfigDaq.Background = new SolidColorBrush(Color.FromRgb(199, 62, 58));

            //
            string link_cmd = "0107" + LinkUsed.ToString("X4");
            SendText(link_cmd);
            link_cmd = "0113" + LinkUsed.ToString("X4");
            SendText(link_cmd);
            SendText("00080001");
            SendText("00010001");
            Thread.Sleep(2000);
            SendText("00010000");
            SendText("01080001");
            Thread.Sleep(2500);
            SendText("00020001");

            byte[] InData = new byte[512];
            int xferLen = 512;
            if (MyDevice != null)
            {
                BulkInEndPoint.XferData(ref InData, ref xferLen, true);
            }
            SendText("00080000");
            if (xferLen != 8)
            {
                lblLinkStatus.Content = "Read Error: " + xferLen.ToString();
                lblLinkStatus.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));
                return;
            }
            uint header1 = 0;
            uint header2 = 0;
            for (int i = 0; i < 4; i++)
            {
                header1 += ((uint)InData[i]) << (8 * i);
            }
            uint link_data = 0;
            for (int i = 0; i < 2; i++)
            {
                link_data += ((uint)InData[i + 4]) << (8 * i);
            }

            for (int i = 0; i < 2; i++)
            {
                header2 += ((uint)InData[i + 6]) << (8 * i);
            }
            if (header1 != 0xDDFFFFDD || header2 != 0xABAB)
            {
                lblLinkStatus.Content = "Header error";
                lblLinkStatus.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));
                return;
            }
            else if (LinkUsed == 0 || link_data != LinkUsed)
            {
                lblLinkStatus.Content = "Readback: 0x" + link_data.ToString("X2");
                lblLinkStatus.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));
                return;
            }
            else
            {
                lblLinkStatus.Content = "Connected";
                IsLinkConnected = true;
                lblLinkStatus.Foreground = new SolidColorBrush(Color.FromRgb(27, 129, 62));
                btnCheckLink.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));

                lblAsicStatus.Content = "Unconfigured";
                IsAsicConfig = false;
                lblAsicStatus.Foreground = new SolidColorBrush(Color.FromRgb(255, 0, 0));

                GetLayerNr();
                //ConverProcessFromDos2Unix();
                ShowText("Link channels checked successfully");
            }
        }

        private void GetLayerNr()
        {
            LayerNr = 0;
            for (int i = 0; i < 8; i++)
            {
                if (((LinkUsed >> i) & 1) == 1)
                {
                    LayerNr += 1;
                }
            }
        }

        ///// 
        
        ///// FEC configuration UI and utility /////
        
        // Step 1: adas1128's two registers
        private void AsicRegConfig()
        {
            SelectBoard(16, true);

            //
            int oversamplingrate = int.Parse(tbxOversamplingRate.Text);
            int mutetime = int.Parse(tbxMuteTime.Text);
            int fullscale = int.Parse(tbxFullscale.Text);
            int pairnumber = int.Parse(tbxPairNr.Text);
            int asic_num = int.Parse(tbxAsicNum.Text);

            string b_s = Convert.ToString(oversamplingrate, 2).PadLeft(5, '0');
            string m_s = Convert.ToString(mutetime, 2).PadLeft(5, '0');
            string p_s = Convert.ToString(pairnumber, 2).PadLeft(5, '0');
            string l_s = Convert.ToString(fullscale, 2).PadLeft(8, '0');

            string reg1_s = "1010" + m_s + b_s + p_s + "10111";
            reg1_s = string.Concat(Enumerable.Repeat(reg1_s, 6));
            string reg0_s = "1000000000000001" + l_s;
            reg0_s = string.Concat(Enumerable.Repeat(reg0_s, 6));
            int n = reg1_s.Length / 16;

            string wren = "0x01020001";
            for (int i = 0; i < n; i++)
            {
                string wr_first = "0x0101100" + i.ToString();
                SendText(wr_first);
                SendText(wren);
                string str = reg1_s.Substring(i * 16, 16);

                int binaryNum = Convert.ToInt32(str, 2);
                string hexStr = binaryNum.ToString("X4");
                string wrStr = "0x0101" + hexStr;
                SendText(wrStr);
                SendText(wren);
                Thread.Sleep(1);
            }

            SendText("0x0101101a");
            SendText(wren);
            SendText("0x01010000");
            SendText(wren);
            Thread.Sleep(1);

            for (int i = 0; i < n; i++)
            {
                string wr_first = "0x0101200" + i.ToString();
                SendText(wr_first);
                SendText(wren);

                string str = reg0_s.Substring(i * 16, 16);
                int binaryNum = Convert.ToInt32(str, 2);
                string hexStr = binaryNum.ToString("X4");
                string wrStr = "0x0101" + hexStr;
                SendText(wrStr);
                SendText(wren);
                Thread.Sleep(1);
            }
            SendText("0x0101201a");
            SendText(wren);
            SendText("0x01010000");
            SendText(wren);
            Thread.Sleep(10);

            // Send the ASIC configuration en 
            SendText("0x01013000");
            SendText(wren);
            SendText("0x01010001");
            SendText(wren);
            Thread.Sleep(6000);

            // send the asic convert en
            SendText("0x0101E000");
            SendText(wren);
            SendText("0x01010001");
            SendText(wren);
        }

        private void btnConfigAsic_Click(object sender, RoutedEventArgs e)
        {
            if (!IsLinkConnected || LinkUsed == 0)
            {
                MessageBox.Show("Please check the link status first", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            if (btnConfigAsic.Content.ToString() == "Setting...")
            {
                return;
            }

            //
            ShowText("Config ASIC chips ...");
            btnConfigAsic.Content = "Setting...";
            btnConfigAsic.Background = new SolidColorBrush(Color.FromRgb(199, 62, 58));

            //
            AsicRegConfig();

            //
            IsAsicConfig = true;

            lblAsicStatus.Content = "Configured";
            lblAsicStatus.Foreground = new SolidColorBrush(Color.FromRgb(27, 129, 62));
            btnConfigAsic.Content = "Set";
            btnConfigAsic.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));

            ShowText("ASIC chips configured successfully!");
        }

        private void btnConfigDaq_Click(object sender, RoutedEventArgs e)
        {
            if (!IsLinkConnected || LinkUsed == 0)
            {
                MessageBox.Show("Please check the link status first", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }
            if (!IsAsicConfig)
            {
                MessageBox.Show("Please config the ASIC chips first", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            // layer Nr
            int layer_nr = int.Parse(tbxTriggerLayerNr.Text);
            if (!IsPedestalMode && layer_nr > LayerNr)
            {
                MessageBox.Show("The trigger layer number is larger than max", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (btnConfigDaq.Content.ToString() == "Configuring")
            {
                return;
            }
            btnConfigDaq.Content = "Configuring...";
            btnConfigDaq.Background = new SolidColorBrush(Color.FromRgb(199, 62, 58));
            ShowText("DAQ Configuring ...");

            //
            if (!IsPedestalMode)
            {
                // enable it
                EnableThreshold();

                // Set Trigger condition
                // strip Nr
                int strip_nr = int.Parse(tbxTriggerStripNr.Text);
                ConfigNormalTriggerStripNr(strip_nr);

                // layer_nr
                ConfigNormalTriggerLayerNr(layer_nr);

                ShowText("Normal trigger mode configured: strip_nr = " + strip_nr + ", layer_nr = " + layer_nr);
            }
            else
            {
                //
                DisableThreshold();

                // Set frq
                double trigger_frenquency = double.Parse(tbxTriggerFrq.Text);
                ConfigPedestalTriggerFrq(trigger_frenquency);

                ShowText("Pedestal trigger mode configured: trigger_frq = " + trigger_frenquency + " Hz");
            }

            //
            IsDaqConfig = true;
            btnConfigDaq.Background = new SolidColorBrush(Color.FromRgb(221, 221, 221));
            btnConfigDaq.Content = "Config";
            //MessageBox.Show("DAQ configured, ready to go", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        private void RstUsbData()
        {
            for (int i = 0; i < QueueSz; i++)
            {
                _ = BulkInEndPoint.Reset();
            }
        }

        private void TransmitStatusChange(bool ONOROff)
        {
            if (ONOROff)
            {
                SendText("01090001");
            }
            else
            {
                SendText("01090000");
            }

        }

        /* broadcast: fec start data transfer */
        private void StartFecDataTransfer()
        {
            SelectBoard(16, true);
            SendText("0x01017000");
            SendText("0x01020001");
            SendText("0x01010001");
            SendText("0x01020001");
            Thread.Sleep(100);
        }

        /* broadcast: fec stop data transfer */
        private void StopFecDataTransfer()
        {
            SelectBoard(16, true);
            SendText("0x01017000");
            SendText("0x01020001");
            SendText("0x01010000");
            SendText("0x01020001");
            Thread.Sleep(100);
        }

        private void EnableThreshold()
        {
            SelectBoard(16, true);
            SendText("0x01019000");
            SendText("0x01020001");
            SendText("0x01010001");
            SendText("0x01020001");

            Thread.Sleep(100);
        }

        private void DisableThreshold()
        {
            SelectBoard(16, true);
            SendText("0x01019000");
            SendText("0x01020001");
            SendText("0x01010000");
            SendText("0x01020001");
            Thread.Sleep(100);
        }

        private void LoadThreshold(String noiseFilePath)
        {
            ShowText("Loading threshold ...");

            //发送各板阈值
            for (int i = 0; i < 16; i++)
            {
                if (((LinkUsed >> i) & 1) == 1)
                {
                    DownloadThreshold(i, noiseFilePath);
                }
            }

            ShowText("Threshold loaded successfully");
        }

        private void TriggerEnable()
        {
            ClearLocalTriggerID();

            if (IsPedestalMode) // pedestal-calib
            {
                EnablePedestalTrigger();
            }
            else
            {
                EnableNormalTrigger(); // normal-trigger
            }
        }

        private void TriggerDisable()
        {
            //关闭外触发模式
            SelectBoard(16, true);
            SendText("0x01016000");
            SendText("0x01020001");
            SendText("0x01010000");
            SendText("0x01020001");

            //关闭触发
            SendText("00040000");
        }

        // the condition for a valid layer trigger to be generated: the number of strips over threshold in x/y dimension
        private void ConfigNormalTriggerStripNr(int strip_number)
        {
            // set the condition of generating single layer's fire signal (i.e. the nr of strips over threshold for each dimenetion)
            string th_s = strip_number.ToString("x");

            SelectBoard(16, true);
            SendText("0x0101D000");
            SendText("0x01020001");
            SendText("0x0101000" + th_s);
            SendText("0x01020001");
            Thread.Sleep(100);
        }

        private void ConfigNormalTriggerLayerNr(int layer_number)
        {
            string th_s = layer_number.ToString("x");
            SendText("0x00f1000" + th_s);
        }

        private void EnableNormalTrigger()
        {
            // clear trigger id
            // ClearLocalTriggerID();

            // 
            // EnableThreshold();

            //
            SendText("00040002");
        }

        private void ConfigPedestalTriggerFrq(double trigger_frequency)
        {
            // config the trigger frq
            double trigger_peroid = 1.0 / trigger_frequency * 1e9;
            int trigger_cmd = (int)(trigger_peroid / 10.0);

            int cmd_num = trigger_cmd & 0xFFFF;
            string cmd_txt = "0007" + cmd_num.ToString("X4");
            SendText(cmd_txt);

            cmd_num = (trigger_cmd >> 16) & 0xFFFF;
            cmd_txt = "0006" + cmd_num.ToString("X4");
            SendText(cmd_txt);
        }

        private void EnablePedestalTrigger()
        {

            //ConfigPedestalTriggerFrq();
            
            //
            SendText("00040003");
            SendText("00050001");
        }

        private void ClearLocalTriggerID()
        {
            SendText("000A0001");
        }

        private void SelectBoard(int brdNum, bool IsBroadcoast = false)
        {
            if (IsBroadcoast)
            {
                SendText("01000010");
            }
            else
            {
                string cmd = "0100" + brdNum.ToString("X4");
                SendText(cmd);
            }
        }

        private void DownloadThreshold(int brdNumber, string noiseFilePath)
        {
            SelectBoard(brdNumber);

            string filename =noiseFilePath+ "\\threshold_link-" + brdNumber.ToString() + "_bin.txt";

            SendFile(filename, false);
        }

        //////

        ///// Data analysis UI and utility /////
        private void OnlineRecProcess(string DataFilename)
        {
            DataFilename = DataFilename.Replace('\\', '/');
            DataFilename = DataFilename.Replace("C:", "c");
            DataFilename = DataFilename.Replace("D:", "d");
            DataFilename = DataFilename.Replace("E:", "e");
            DataFilename = DataFilename.Replace("F:", "f");

            string currentPath = System.IO.Directory.GetCurrentDirectory();
            currentPath = currentPath.Replace(@"\", "/");
            currentPath = currentPath.Replace("C:", "c");
            currentPath = currentPath.Replace("D:", "d");
            currentPath = currentPath.Replace("E:", "e");
            currentPath = currentPath.Replace("F:", "f");



            string strCmdText = "/c wsl -e bash -c \"/mnt/" + currentPath + "/wsl_process/bin/data_process.sh -M -d /mnt/" + DataFilename
                + " -L 0x" + LinkUsed.ToString("X2")
                + " -k " + LayerNr.ToString()
                + " -r " + RunId.ToString()
                + " -i " + AcqInterval.ToString();
            //+ " -j /mnt/" + currentPath + "/" + fOnlineDir + " \"";

            if (IsPushData)
                strCmdText += " -p";

            if (!IsStdPedestal)
            {
                strCmdText += " -B /mnt/" + PedestalPath;
            }

            DisplayOnlineInfo dp = new DisplayOnlineInfo((bool ready) => UpdateOnlineStatus(ready));
            if (IsFirtDaqLoop)
            {
                CallWslCmd(strCmdText, true);
                Dispatcher.Invoke(dp, true);
            }
            else
                CallWslCmd(strCmdText);
        }

        void ConverProcessFromDos2Unix()
        {
            string currentPath = System.IO.Directory.GetCurrentDirectory();
            currentPath = currentPath.Replace(@"\", "/");
            currentPath = currentPath.Replace("C:", "c");
            currentPath = currentPath.Replace("D:", "d");
            currentPath = currentPath.Replace("E:", "e");
            currentPath = currentPath.Replace("F:", "f");
            string strCmdText = "/c wsl -e bash -c \"dos2unix /mnt/" + currentPath + "/wsl_process/bin/data_process.sh" + " \"";
            CallWslCmd(strCmdText);
        }

        void CallWslCmd(string cmd_txt, bool b_wait=false)
        {
            System.Diagnostics.Process process = new System.Diagnostics.Process();
            System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
            startInfo.FileName = "cmd.exe";
            startInfo.Arguments = cmd_txt;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true;
            process.StartInfo = startInfo;
            process.Start();
            if(b_wait) process.WaitForExit();
        }

        private void InvokeEveApp()
        {
            string currentPath = System.IO.Directory.GetCurrentDirectory();
            string filePath = System.IO.Path.Combine(currentPath, "eve", "app.exe");
            if (!File.Exists(filePath))
            {
                ShowText($"Eve app {filePath} not found!");
            }

            System.Diagnostics.Process process = new System.Diagnostics.Process();
            System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
            startInfo.FileName = filePath;
            startInfo.UseShellExecute = false;
            startInfo.CreateNoWindow = true;
            startInfo.WorkingDirectory = System.IO.Path.Combine(tbxReceiveDataPath.Text, "json");
            process.StartInfo = startInfo;
            process.Start();
        }

        private void cbxTriggerMode_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            btnConfigDaq.Background = new SolidColorBrush(Color.FromRgb(199, 62, 58));

            if (cbxTriggerMode.SelectedIndex == 1) // pedestal-calib
            {
                splTriggerFrq.IsEnabled = true;
                splTriggerConditionSetting.IsEnabled = false;
                splThresholdSetting.IsEnabled = false;
                btnLoadThreshold.IsEnabled = false;

                IsPedestalMode = true;
            }
            else if (cbxTriggerMode.SelectedIndex == 0) // normal-acq
            {
                splTriggerFrq.IsEnabled = false;
                splTriggerConditionSetting.IsEnabled = true;
                splThresholdSetting.IsEnabled = true;
                btnLoadThreshold.IsEnabled = true;

                IsPedestalMode = false;
            }
        }

        private void cbxThreshMode_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (cbxThreshMode.SelectedIndex == 0)
            {
                IsCustomThresh = false;
                splCustomThreshold.IsEnabled = false;
            }
            else
            {
                IsCustomThresh = true;
                splCustomThreshold.IsEnabled = true;
            }
        }

        private void cbxDataProcessOrNot_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (cbxDataProcessOrNot.SelectedIndex == 0)
            {
                IsProcessData = true;
            }
            else
            {
                IsProcessData = false;
            }
        }
        /////

        ////// Info panel ///////
        private void ShowText(string text)
        { 
            tbxInfo.Text = " " + text;
        }

        ////// Other UIs ////////

        private void btnLoadCustomThreshold_Click(object sender, RoutedEventArgs e)
        {
            var fldrDlg = new System.Windows.Forms.FolderBrowserDialog();
            fldrDlg.SelectedPath = tbxCustomThresholdPath.Text;

            if (fldrDlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                tbxCustomThresholdPath.Text = fldrDlg.SelectedPath;
            }
        }

        ////// Utilities ///////
        private void CleanOnlineDirectory()
        {
            string currentPath = System.IO.Directory.GetCurrentDirectory();
            string filePath = System.IO.Path.Combine(currentPath, fOnlineDir);
            if (Directory.Exists(filePath))
            {
                Directory.Delete(filePath, true);
            }
            Directory.CreateDirectory(filePath);
        }

        private void CalcNoiseProcess()
        {
            // Get the noise data path in string
            string NoiseDataFilename = tbxReceiveDataPath.Text + @"\" + tbxFileName.Text + "-" + lblFileLoop.Content.ToString() + ".dat";
            if (File.Exists(NoiseDataFilename) == false)
            {
                MessageBox.Show("The noise data file is not exist", "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            // Get current path 
            string currentPath = System.IO.Directory.GetCurrentDirectory();

            // Change win disk label to wsl mount label
            NoiseDataFilename = NoiseDataFilename.Replace('\\', '/');
            NoiseDataFilename = NoiseDataFilename.Replace("C:", "c");
            NoiseDataFilename = NoiseDataFilename.Replace("D:", "d");
            NoiseDataFilename = NoiseDataFilename.Replace("E:", "e");
            NoiseDataFilename = NoiseDataFilename.Replace("F:", "f");

            currentPath = currentPath.Replace(@"\", "/");
            currentPath = currentPath.Replace("C:", "c");
            currentPath = currentPath.Replace("D:", "d");
            currentPath = currentPath.Replace("E:", "e");
            currentPath = currentPath.Replace("F:", "f");

            // Call wsl program to calculate noise
            string strCmdText = "/c wsl -e bash -c \"/mnt/" + currentPath + "/wsl_process/bin/data_process.sh -N"
                                + " -t " + tbxThreshLimit.Text
                                + " -d /mnt/" + NoiseDataFilename
                                + " -L 0x" + LinkUsed.ToString("X2")
                                + " \"";
            CallWslCmd(strCmdText, true);
        }

        private byte[] HexStringToByteArray(string HexString)
        {
            int StringNum = HexString.Length;
            byte[] hexBytes = new byte[StringNum / 2];
            for (int i = 0; i < StringNum; i += 2)
            {
                hexBytes[i / 2] = Convert.ToByte(HexString.Substring(i, 2), 16);
            }
            return hexBytes;
        }

        private bool SendFile(string fileName, bool IsDisplay = true)
        {
            byte[] RawCmdBytes;
            if (fileName == null || !File.Exists(fileName))
            {
                MessageBox.Show("File not found\n" + fileName, "File ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                return false;
            }
            using (StreamReader cmdFileStream = new StreamReader(fileName))
            {
                string[] hexMarks = new string[] { "0x", "0X" };
                string CmdString = cmdFileStream.ReadLine();
                int LineNum = 0;
                while (CmdString != null)
                {
                    CmdString = CmdString.Replace(" ", string.Empty);
                    if (CmdString.Contains("//"))
                    {
                        CmdString = cmdFileStream.ReadLine();
                        LineNum++;
                        continue;
                    }
                    if (CmdString.Contains("#"))
                    {
                        CmdString = cmdFileStream.ReadLine();
                        LineNum++;
                        continue;
                    }
                    if (CmdString == "")
                    {
                        CmdString = cmdFileStream.ReadLine();
                        LineNum++;
                        continue;
                    }
                    if (CmdString[0] == '&')
                    {
                        int sleep_time;
                        bool success = int.TryParse(CmdString.Substring(1, CmdString.Length - 1), out sleep_time);
                        if (success)
                        {
                            Thread.Sleep(sleep_time);
                        }
                        else
                        {
                            MessageBox.Show("Invalid sleep time at Line: " + LineNum.ToString() + " Value" + CmdString, "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                        }
                        CmdString = cmdFileStream.ReadLine();
                        LineNum++;
                        continue;
                    }
                    if (CmdString.Contains("0X") || CmdString.Contains("0x"))
                    {
                        string[] cmdSubString = CmdString.Split(hexMarks, StringSplitOptions.RemoveEmptyEntries);
                        CmdString = string.Join("", cmdSubString);
                    }
                    if (CheckStringFormat.CheckHex(CmdString))
                    {
                        RawCmdBytes = HexStringToByteArray(CmdString);
                        CommandSend(RawCmdBytes, BulkOutEndPoint, IsLittleEndian);
                    }
                    else
                    {
                        MessageBox.Show("Content Error at line: " + LineNum.ToString() + " Content: " + CmdString, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
                    }
                    CmdString = cmdFileStream.ReadLine();
                    LineNum++;
                }
                if (IsDisplay)
                {
                    tbxInfo.AppendText("File " + fileName + " send\n");
                }
            }
            return true;
        }

        private void SendText(string text)
        {
            string CmdIn = text.Replace(" ", string.Empty);
            string[] hexMarks = new string[] { "0x", "0X" };
            if (CmdIn.Contains("0X") || CmdIn.Contains("0x"))
            {
                string[] cmdSubString = CmdIn.Split(hexMarks, StringSplitOptions.RemoveEmptyEntries);
                CmdIn = string.Join("", cmdSubString);
            }
            if (CmdIn == "" || !CheckStringFormat.CheckHex(CmdIn))
            {
                MessageBox.Show("Please input the cmd in HEX", "Command ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            if (CmdIn.Length % 4 != 0)
            {
                MessageBox.Show("Please check the length of the input cmd. Cmd not a multiple of 4", "Command ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            byte[] RawCmdBytes;
            RawCmdBytes = HexStringToByteArray(CmdIn);

            CommandSend(RawCmdBytes, BulkOutEndPoint, IsLittleEndian);
        }

        private bool CommandSend(byte[] CmdBytes, CyBulkEndPoint usbBulkOutEndPoint)
        {
            int CmdLength = CmdBytes.Length;
            if (CmdLength == 0)
                return false;
            if (usbBulkOutEndPoint == null)
            {
                MessageBox.Show("USB not connected", "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
                return false;
            }
            return usbBulkOutEndPoint.XferData(ref CmdBytes, ref CmdLength);
        }

        private bool CommandSend(byte[] CmdBytes, CyBulkEndPoint usbBulkOutEndPoint, bool IsLittleEndian)
        {
            byte[] CmdBytesToUsb = CommandLengthCheck(CmdBytes);
            if (IsLittleEndian)
            {
                CmdBytesToUsb = CommandEndianChange(CmdBytesToUsb);
            }
            return CommandSend(CmdBytesToUsb, usbBulkOutEndPoint);
        }

        private byte[] CommandEndianChange(byte[] CmdBytes)
        {
            int CmdLength = CmdBytes.Length;
            byte cmdTemp;

            for (int i = 0; i < CmdLength; i += 4)
            {
                cmdTemp = CmdBytes[i];
                CmdBytes[i] = CmdBytes[i + 3];
                CmdBytes[i + 3] = cmdTemp;
                cmdTemp = CmdBytes[i + 1];
                CmdBytes[i + 1] = CmdBytes[i + 2];
                CmdBytes[i + 2] = cmdTemp;
            }

            return CmdBytes;
        }

        private void btnOnline_Click(object sender, RoutedEventArgs e)
        {
            InvokeEveApp();
        }

        private void btnCalcNoise_Click(object sender, RoutedEventArgs e)
        {
            CalcNoiseProcess();
        }

        private void btnLoadThreshold_Click(object sender, RoutedEventArgs e)
        {
            // download threshold value into fec
            ShowText("Load threshold");
            if (IsCustomThresh)
            {
                LoadThreshold(tbxCustomThresholdPath.Text);
                PedestalPath = tbxCustomThresholdPath.Text;
                PedestalPath = PedestalPath.Replace("\\threshold", "");
            }
            else
            {
                LoadThreshold(tbxReceiveDataPath.Text+"\\threshold");
                PedestalPath = tbxReceiveDataPath.Text;
            }
            PedestalPath = PedestalPath.Replace('\\', '/');
            PedestalPath = PedestalPath.Replace("C:", "c");
            PedestalPath = PedestalPath.Replace("D:", "d");
            PedestalPath = PedestalPath.Replace("E:", "e");
            PedestalPath = PedestalPath.Replace("F:", "f");
            Thread.Sleep(10);

            // enable it
            //EnableThreshold();
        }

        private void cbxDataPushOrNot_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (cbxDataPushOrNot.SelectedIndex == 0)
            {
                IsPushData = true;
            }
            else
            {
                IsPushData = false;
            }
        }

        private void cbxStdPedestalOrNot_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (cbxStdPedestalOrNot.SelectedIndex == 0)
            {
                IsStdPedestal = true;
            }
            else
            {
                IsStdPedestal = false;
            }
        }

        private byte[] CommandLengthCheck(byte[] CmdBytes)
        {
            int CmdLength = CmdBytes.Length;
            int CmdNum;
            int CmdNumRes;
            int NewCmdLength;
            int CmdLengthDivider = 4;
            CmdNum = CmdLength / CmdLengthDivider;
            CmdNumRes = CmdLength % CmdLengthDivider;
            if (CmdNumRes != 0)
            {
                NewCmdLength = (CmdNum + 1) * CmdLengthDivider;
            }
            else
            {
                NewCmdLength = CmdNum * CmdLengthDivider;
            }
            byte[] NewCmdBytes = new byte[NewCmdLength];
            for (int i = 0; i < CmdNum * CmdLengthDivider; i++)
            {
                NewCmdBytes[i] = CmdBytes[i];
            }
            for (int i = 0; i < CmdNumRes; i++)
            {
                NewCmdBytes[NewCmdLength - i - 1] = CmdBytes[CmdLength - i - 1];
            }

            return NewCmdBytes;
        }
        /////
    }
}
